﻿/// <reference path="jquery-1.6.2.js" />
/// <reference path="jquery-ui-1.8.16.custom.js" />
$.widget("ui.mingrid", {
    /*
    events:
    load: called after _load. properties e.g. selectedColumns(), droppedColumns() are updated.
    */
    options: {
        height: 300,
        width: 800,
        columns: null,
        isDraggable: true,
        source: [],
        //dateFormat: "%F", //TODO: add formatting property to columndefs (for Date, float-$ values)
        autoLayout: true   //if true - all columns render to max. width and any columns which exceed the grid's defined width are "overflow" and not rendered.
    },
    _create: function () {
        var element = this.element;
        this.source(this.options["source"]);

        this._load(element, this.source(), this.options['columns'], this.options['height'], this.options['width']);

        this._setRowSelectHandler(true);
    },
    destroy: function () {
        $.Widget.prototype.destroy.apply(this, arguments);
        $("thead>tr>td", this.element).draggable("destroy");
        $("thead>tr>td", this.element).droppable("destroy");
        this.element.unbind();
        this.source = null;
    },
    /*
    columns: { } object map, convention here is that the key is ordinal (0,1...n)
    */
    _load: function (element, source, desiredColumns, height, width) {
        var scrollbarwidth = jQuery.ui.mingrid.scrollbarwidth(),
            autoLayout = this.options.autoLayout;

        var renderFirst2Rows = function (columndefs) {
            var i = 0, j = 0, rows = { header_row: "", body_row: "" }, table_body_html = [], table_header_html = [];
            table_body_html[i++] = "<tr data-row-index='0'>";
            table_header_html[j++] = "<tr>";
            for (var ordinal in columndefs) {
                var name = columndefs[ordinal]['name'], width = columndefs[ordinal]['width'] + "px";
                table_body_html[i++] = "<td data-cell-ordinal='" + ordinal + "' "
                    + "style='width: " + width + ";"
                    + "min-width: " + width + ";"
                    + "max-width: " + width + ";"
                    + "overflow:hidden;'>";
                table_body_html[i++] = source[0][name];
                table_body_html[i++] = "</td>";
                table_header_html[j++] = "<td data-cell-ordinal='" + ordinal + "' "
                    + "class='ui-state-default ui-button-text-icon-primary' "
                    + "style='width: " + width + ";"
                    + "min-width: " + width + ";"
                    + "max-width: " + width + ";"
                    + "overflow:hidden;'>";
                // "<td class='ui-state-default ui-button-text-icon-primary' "
                //                      + "data-cell-ordinal='" + ordinal + "' style='width: " + width + ";overflow:hidden;'>";
                table_header_html[j++] = name;
                table_header_html[j++] = "</td>";
            }
            table_body_html[i++] = "</tr>";
            table_header_html[j++] = "</tr>";
            rows.header_row = table_header_html.join("");
            rows.body_row = table_body_html.join("");
            return rows;
        };

        var renderMainBody = function (columndefs) {
            var table_body_html = [], i = 0, j = 1, n = source.length;
            for (j = 1; j < n; j++) {
                table_body_html[i++] = "<tr data-row-index='" + j + "'>";
                for (var ordinal in columndefs) {
                    var name = columndefs[ordinal]['name'], width = columndefs[ordinal]['width'];
                    //table_body_html[i++] = "<td data-cell-ordinal='" + ordinal + "'>";
                    table_body_html[i++] = "<td data-cell-ordinal='" + ordinal + "' "
                    //+ "style='max-width: " + width + ";"                    
                    + "style='"
                    + "overflow:hidden;'>";
                    table_body_html[i++] = source[j][name];
                    table_body_html[i++] = "</td>";
                }
                table_body_html[i++] = "</tr>";
            }
            return table_body_html.join("");
        };

        var loadDefined = function () {
            var html = [], i = 0, j = 0, n = 0, first2rows = null, htmlstring = "";
            this.selectedColumns(desiredColumns);
            if (!this.columns()) {
                //get the complete set of columns, for later use. Does not represent the selected columns at all.
                this.columns(jQuery.ui.mingrid.autoGenerateColumnDefinitions(source[0], 80));
                //fullcolumndefs = $.extend( fullcolumndefs, this.selectedColumns());
            }

            first2rows = renderFirst2Rows(this.selectedColumns());

            //begin table header
            html[i++] = "<div class='table-header-wrapper'>"
            html[i++] = "<table class ='grid table-header'><thead>";
            html[i++] = first2rows.header_row;
            //end table header
            html[i++] = "</thead></table>";
            html[i++] = "</div>";

            //begin table body:
            html[i++] = "<div class='table-body-wrapper' style='height: " + height + "px; overflow-y: auto; overflow-x:hidden;'>"
            html[i++] = "<table class='grid table-body'>";
            html[i++] = first2rows.body_row;
            html[i++] = renderMainBody(this.selectedColumns());

            //end table body:
            html[i++] = "</table>";
            html[i++] = "</div>";
            htmlstring = html.join("");
            $(element).append(htmlstring);
        };

        var loadAuto = function () {
            var table_body, table_header,
                table_body_html = [], table_header_html = [],
                header_cells, body_cells, hcw, bcw = 0, diff = 0,
                cell = null, targetwidth = 0, rows = null,
                selected = null, columns = this.columns(),
                i = 0, j = 0, n = 0;

            $(element).append("<div class='table-header-wrapper'>"
            + "<table class ='grid table-header'><thead></thead></table>"
            + "</div>"
            + "<div class='table-body-wrapper' style='height: " + height + "px; overflow-y: auto; overflow-x:hidden;'>"
            + "<table class='grid table-body' />"
            + "</div>");
            table_body = $("table.table-body", element);
            table_header = $("table.table-header>thead", element);

            //allow cells to expand to max. widths. this is so that every cell takes up max. 1 line (does not wrap text).
            $(".table-body-wrapper", element).css("width", "3000px");
            $(".table-header-wrapper", element).css("width", "3000px");

            if (!columns) {
                columns = jQuery.ui.mingrid.autoGenerateColumnDefinitions(source[0], 0);
            }
            if (desiredColumns) {
                selected = desiredColumns; //this.selectedColumns(desiredColumns);
            } else {
                selected = columns;
            }

            //approach to get correct widths since different chars may differ in width:
            //works but this is THE bottleneck of this rendering function.
            //TODO: if user specifies column defs (for max. speed), then skip the following:

            rows = renderFirst2Rows(selected);

            table_header.append(rows.header_row);
            table_body.append(rows.body_row);
            table_body.append(renderMainBody(selected));

            header_cells = $("tr:eq(0) td", table_header);
            body_cells = $("tr:eq(0) td", table_body);
            for (i = 0; i < header_cells.length; i++) {
                hcw = $(header_cells[i]).width();
                bcw = $(body_cells[i]).width();

                if (hcw > bcw) {//hcw > bcw
                    cell = $(body_cells[i]);
                    cell.css({ "width": hcw });
                    targetwidth = hcw;
                } else {
                    cell = $(header_cells[i]);
                    cell.css({ "width": bcw });
                    targetwidth = bcw;
                }
                diff = targetwidth - cell.width();
                if (diff > 0) { // necessary hack/workaround                
                    targetwidth = targetwidth + diff;
                    cell.css({ "width": targetwidth });
                }
                //i is ordinal                                                
                if (columns) {
                    columns[i]['width'] = targetwidth;
                    columns[i]['outerWidth'] = cell.outerWidth();
                }
                if (selected[i]) {
                    selected[i]['width'] = targetwidth;
                    selected[i]['outerWidth'] = cell.outerWidth();
                }                
            }

            /*if columns don't fit, start excluding them until they do fit.*/
            
            //use this sum function since selectedColumns property is not available until the end of _load.
            var sumColumnWidths = function () {
                var sum = 0;
                for (var ord in selected) {
                    sum += selected[ord]['outerWidth'];
                }
                return sum;
            }
            for (var ordinal in selected) {
                if (sumColumnWidths() > (width - scrollbarwidth)) {
                    delete selected[ordinal];
                    //$("td[data-cell-ordinal='" + ordinal + "']").css("display", "none")//possible but screws up jQuery UI's Selectable interaction
                    $("td[data-cell-ordinal='" + ordinal + "']").remove();
                }
            }                
            

            this.selectedColumns(selected);
            this.columns(columns);
            $(".table-body-wrapper", element).css("width", "auto");
            $(".table-header-wrapper", element).css("width", "auto");
        }; //loadAuto

        var setInteractions = function () {
            if (this.options.isDraggable) {
                var dropAndSwap = (function (element, source, height, width, columndefs) {
                    var widget = this;
                    return function (event, ui) {
                        if (!$(this).is("td")) {
                            return; //prevent other, non-td elements from inadvertently getting dropped here.
                        };
                        var to = $(this).attr("data-cell-ordinal");
                        var from = $(ui['draggable']).attr("data-cell-ordinal");
                        //swap:
                        if (to && from) {
                            var coldef = columndefs[to];
                            columndefs[to] = columndefs[from];
                            columndefs[from] = coldef;
                            //$("tr:eq(0)>td", grid).droppable("destroy");
                            widget._load.call(widget, element, source, columndefs, height, width);
                        }
                    };
                }).call(this, element, source, height, width, this.selectedColumns());

                $("thead tr:eq(0)>td", element).draggable({
                    helper: 'clone', //since the td is dragged onto the grid itself for column-swapping, keep it as a td
                    connectToSortable: this.options.connectToSortable,
                    drag: function (event, ui) {

                    },
                    stop: function (event, ui) {

                    }
                });
                $("thead tr:eq(0)>td", element).droppable({
                    drop: dropAndSwap,
                    hoverClass: "ui-state-active"
                , over: function (event, ui) {
                    var ordinal = $(this).attr("data-cell-ordinal");
                    $("td[data-cell-ordinal='" + ordinal + "']", ".table-body").addClass("hover");
                }
                , out: function (event, ui) {
                    var ordinal = $(this).attr("data-cell-ordinal");
                    $("td[data-cell-ordinal='" + ordinal + "']", ".table-body").removeClass("hover");
                }
                });

            }
        }

        $(element).empty();
        $(element).attr("style", ("width: " + width + "px; overflow-x:hidden; overflow-y:hidden"));

        if (autoLayout) {
            loadAuto.apply(this, arguments);
        } else {
            loadDefined.apply(this, arguments);
        }

        setInteractions.apply(this, arguments);

        this._trigger('load');
    },


    load: function (source, desiredColumns) {
        this.source(source);
        this.options.desiredColumns = desiredColumns;
        this._load(this.element, this.source(), this.options.desiredColumns, this.options.height, this.options.width);
    },
    filter: function (filter_map) {

        var filtered = this.source().filter(function (object, i) {
            var ismatch = false;
            for (var key in filter_map) {
                var caller = new String(object[key]).toLowerCase();
                var searchValue = new String(filter_map[key]).toLowerCase();
                ismatch = ismatch || caller.indexOf(searchValue) > -1;
            }
            return ismatch;
        });
        this._load(this.element, filtered, this.options.desiredColumns, this.options.height, this.options.width);
    },

    //if the user selection exceeds the width of the table, add the user's selected column, but remove some other column.
    sort: function (columnnames) {

        if ((columnnames instanceof Array) && columnnames.length > 0) {
            var data = this.source().slice(0, this.source().length);
            data.sort(by(function (o) {
                var cols = columnnames.map(function (element, index, array) { return o[element]; });
                return cols;
            }));
            this.source(data);
        }
        this._load(this.element, this.source(), this.selectedColumns(), this.options['height'], this.options['width']);
    },
    disable: function (event) {

        if (event === 'rowselect') {
            this._setRowSelectHandler(false);
        }
        else {
            this._setRowSelectHandler(false);
            $.Widget.prototype.disable.apply(this, arguments);
        }

    },
    enable: function (event) {
        if (event === 'rowselect') {
            this._setRowSelectHandler(true);
        }
        else {
            this._setRowSelectHandler(true);
            $.Widget.prototype.enable.apply(this, arguments);
        }

    },

    //event: 'rowselect'
    _setRowSelectHandler: (function (enabled) {
        var element = this.element,
            target = $(".table-body tr", element);

        target.unbind("select").unbind("mousedown");
        if (!enabled) {
            return;
        }

        var handler = function (event) {
            if (event.srcElement.tagName === "TD") {
                var tr = $(event.srcElement).parent();
                var index = parseInt(tr.attr("data-row-index"));
                var ui = { "data": this.source()[index], "index": index };
                this._trigger('rowselect', event, ui); //$(element).trigger("selected", [event, ui]);                
                //$("tr", element).removeClass("selected");
                //tr.addClass("ui-widget-content selected");
            }
        }
        target.bind("select mousedown", handler.bind(this));
    }),
    source: (function () {
        var _source; //TODO: initialize
        return function () {
            //get
            if (arguments.length === 0) {
                return _source;
            }
            //set
            else if (arguments[0] !== _source) {
                _source = arguments[0];
            }
        };
    })(),
    columns:
     (function () {
         var _allcolumns;
         return function () {
             //get
             if (arguments.length === 0 && _allcolumns) {
                 return $.extend({}, _allcolumns)
             }
             else if (arguments.length === 0 && !_allcolumns) {
                 return null;
             }
             //set
             else if (arguments[0] !== _allcolumns) {
                 _allcolumns = arguments[0];
             }
         };
     })(),
    selectedColumns: (function () {
        var _selected; //TODO: initialize
        return function () {
            //get
            if (arguments.length === 0 && _selected) {
                return $.extend({}, _selected);
            }
            else if (arguments.length === 0 && !_selected) {
                return null;
            }
            //set
            else if (arguments[0] !== _selected) {
                _selected = arguments[0];
            }
        };
    })(),
    //read-only
    droppedColumns: function () {
        var origincolumndefs, selectedcolumndefs;

        if (arguments.length === 0) {
            origincolumndefs = this.columns();
            selectedcolumndefs = this.selectedColumns();
        } else {
            origincolumndefs = $.extend({}, arguments[0]);
            selectedcolumndefs = $.extend({}, arguments[1]);
        }
        //original - selected = dropped
        for (var s in selectedcolumndefs) {
            if (typeof origincolumndefs[s] !== "undefined") {
                delete origincolumndefs[s];
            }
        }
        return origincolumndefs;
    }
});
(function () {
    jQuery.ui.mingrid.ColumnDefinition = function (name, isNumeric, width) {
        this.name = name;
        this.isNumeric = isNumeric;
        this.width = width || 0;
    };
    jQuery.ui.mingrid.ColumnDefinition.prototype.outerWidth = 0;

    jQuery.ui.mingrid.autoGenerateColumnDefinitions = function (rowobject, column_width) {
        var columndefs = {}, //$.extend({}, selected), 
                ordinal = 0,
                column_width = column_width || 80;

        for (var key in rowobject) {
            columndefs[ordinal++] = new jQuery.ui.mingrid.ColumnDefinition(key, typeof rowobject[key] === "number", column_width);
        }
        return columndefs;
    };
    //should be called after document.ready
    jQuery.ui.mingrid.scrollbarwidth = (function () {
        var scrollbarwidth = 0;
        return function () {
            if (scrollbarwidth) return scrollbarwidth;
            var div = $('<div style="width:50px;height:50px;overflow:hidden;position:absolute;top:-200px;left:-200px;"><div style="height:100px;"></div></div>');
            $('body').append(div);
            var w0 = $('div', div).innerWidth();
            div.css('overflow-y', 'auto');
            var w1 = $('div', div).innerWidth();
            $(div).remove();
            scrollbarwidth = (w0 - w1);
            return scrollbarwidth;
        };
    } ());


} ());